﻿using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

namespace Neatly.Helper
{
    // AES加密工具
    public static class AesEncryptUtility
    {
        private const string SERVER_PASSPHRASE  = "m8kDq3SZaPRPgQUG";
        private static readonly byte[] KeyBuffer = new byte[32];
        private static readonly byte[] IvBuffer = new byte[16];
        private static readonly List<byte> EncryptBuffer = new List<byte>();
        
        public static string Encrypt(string str)
        {
            var aes = new AesCryptoServiceProvider();
            // _aes.BlockSize = 128;
            //_aes.KeySize = 256;
            var randomBytes = GenerateRandomBytes();
            var keyAndIv = GetKeyAndIv(SERVER_PASSPHRASE, randomBytes);
            Array.Copy(keyAndIv, 0, KeyBuffer, 0, 32);
            Array.Copy(keyAndIv, 32, IvBuffer, 0, 16);
            
            aes.Key = KeyBuffer;
            aes.IV = IvBuffer;
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.CBC;

            var encryptBytes = Encoding.UTF8.GetBytes(str);
            using var crypto = aes.CreateEncryptor(aes.Key, aes.IV);
            var encryptedBytes = crypto.TransformFinalBlock(encryptBytes, 0, encryptBytes.Length);
            
            EncryptBuffer.Clear();
            EncryptBuffer.AddRange(randomBytes);
            EncryptBuffer.AddRange(encryptedBytes);
            return Convert.ToBase64String(EncryptBuffer.ToArray());
        }

        public static string Decrypt(string str)
        {
            var encryptBytes = Convert.FromBase64String(str);
            if (encryptBytes.Length < 8)
            {
                return string.Empty;
            }
            
            // random bytes
            Array.Copy(encryptBytes, 0, RandomBytesBuffer, 0, RandomBytesLength);
            
            var keyAndIv = GetKeyAndIv(SERVER_PASSPHRASE, RandomBytesBuffer);
            Array.Copy(keyAndIv, 0, KeyBuffer, 0, 32);
            Array.Copy(keyAndIv, 32, IvBuffer, 0, 16);

            var aes = new AesCryptoServiceProvider();
            
            aes.Key = KeyBuffer;
            aes.IV = IvBuffer;
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.CBC;
            
            using var decrypt = aes.CreateDecryptor(aes.Key, aes.IV);
            var decryptedBytes = decrypt.TransformFinalBlock(encryptBytes, RandomBytesLength, encryptBytes.Length - RandomBytesLength);
            
            return Encoding.UTF8.GetString(decryptedBytes);
        }

        #region [Get Key And Iv]

        private const int KeyBufferLimit = 128;
        private const int MD5BytesLength = 16;
        private const int GetKeyAndIvResultLength = 48;
        
        private static readonly byte[] GetKeyAndIvResultBuffer = new byte[48];
        private static readonly byte[] KeyBuffer2 = new byte[KeyBufferLimit];
        private static readonly byte[] HashInputBuffer = new byte[KeyBufferLimit + RandomBytesLength + MD5BytesLength];

        private static byte[] GetKeyAndIv(string passphrase, byte[] randomBytes)
        {
            var keyLength = Encoding.UTF8.GetBytes(passphrase, 0, Encoding.UTF8.GetByteCount(passphrase), KeyBuffer2, 0);
            
            using var md = MD5.Create();

            var resultSize = 0;
            var inputIndex = 0;

            while (resultSize < GetKeyAndIvResultLength)
            {
                Array.Copy(KeyBuffer2, 0, HashInputBuffer, inputIndex, keyLength);
                inputIndex += keyLength;
                
                Array.Copy(randomBytes, 0, HashInputBuffer, inputIndex, RandomBytesLength);
                inputIndex += RandomBytesLength;
                
                // md5返回16字节长度的byte数组
                var hashBytes = md.ComputeHash(HashInputBuffer, 0, inputIndex);
                
                Array.Copy(hashBytes, 0, HashInputBuffer, 0, MD5BytesLength);
                inputIndex = MD5BytesLength;

                var copyLength = Math.Min(MD5BytesLength, GetKeyAndIvResultLength - resultSize);
                Array.Copy(hashBytes, 0, GetKeyAndIvResultBuffer, resultSize, copyLength);
                resultSize += copyLength;
            }

            return GetKeyAndIvResultBuffer;
        }

        #endregion

        #region [Generate Random String]
        
        private const int RandomBytesLength = 8;
        private static readonly byte[] RandomBytesBuffer = new byte[RandomBytesLength];
        private static readonly Random Random = new Random();
        private const string RandomChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        
        private static byte[] GenerateRandomBytes()
        {
            for (var i = 0; i < RandomBytesLength; i++)
            {
                var index = Random.Next(RandomChars.Length);
                RandomBytesBuffer[i] = (byte)RandomChars[index];
            }
            
            return RandomBytesBuffer;
        }

        #endregion
    }
}